package ir;

import com.sun.org.apache.xml.internal.resolver.readers.ExtendedXMLCatalogReader;
import parser.CmmLex;
import java.util.*;
import java.util.regex.Pattern;

public class IRExecuter {

    public static int currentLine;
    public static Symbol newsym;
    public static IRLine currentIR;
    public static LinkedList<Symbol> symlist;//store all variable
    public static int level=0;
    public static String finaloutput="";//final output
    public static ArrayList<IRLine> IR;

    public static final int
            LABEL=0, JMP = 1,MOV = 2, WRITE = 3, DECL = 4, ASSIGN = 5,
            PLUS = 6, MINUS = 7, MUL = 8, DIV = 9, MOD = 10, BIGGER_THAN = 11,
            LESS_THAN = 12, NO_LESS = 13, NO_MORE = 14, EQ = 15,
            UNEQ = 16, LOG_OR = 17, LOG_AND = 18, LOG_NOT = 19,
            Minus = 20, READ = 21, IN = 22, OUT = 23;

    //Registers
    public static int TYPE = 0;
    public static Stack<Integer> VARIABLE;
    public static Stack<Value> COMPARE;
    public static Stack<Value> EXPR;

    public IRExecuter(ArrayList<IRLine> i){
        IR = i;
        currentLine = 0;
        symlist = new LinkedList<Symbol>();
        COMPARE = new Stack<Value>();
        EXPR    = new Stack<Value>();
        VARIABLE= new Stack<Integer>();
    }

	public void excute() throws Exception{
		for(currentLine=0;currentLine<IR.size();currentLine++){
			currentIR=IR.get(currentLine);
			switch(currentIR.first){
                case LABEL:
                    break;
                case JMP:
                    JMP();
                    break;
                case MOV:
                    MOV();
                    break;
                case WRITE:
                    WRITE();
                    break;
                case DECL:
                    DECL();
                    break;
                case ASSIGN:
                    ASSIGN();
                    break;
                case PLUS:
                case MINUS:
                    PLUSMINUS();
                    break;
                case MUL:
                case DIV:
                case MOD:
                    MULDIV();
                    break;
                case BIGGER_THAN:
                case LESS_THAN:
                case NO_LESS:
                case NO_MORE:
                case EQ:
                case UNEQ:
                    RELATION();
                    break;
                case LOG_NOT:
                    LOG_NOT();
                    break;
                case Minus:
                    Minus();
                    break;
                case READ:
                    READ();
                    break;
                case IN:
                    level++;
                    break;
                case OUT:
                    level--;
                    break;
                default:
                    throw new InterruptedException("Unexpected IR ");
			}
		}
	}

    public void MOV(){
    //(MOV, A, null, COMPARE)
        // (MOV, int/double/bool, null, TYPE)
        // (MOV, ID, null/EXPR, VARIABLE)
        switch (currentIR.dest){
            case IRProduceListener.TYPE:
                TYPE = Integer.parseInt(currentIR.second);
                break;
            case IRProduceListener.VARIABLE:
                int a = isValExist(currentIR.second);
                if(currentIR.third!=null)
                    a -= EXPR.pop().mInt;
                VARIABLE.push(a);
                break;
            case IRProduceListener.COMPARE:
                if(currentIR.second.equals(IRProduceListener.EXPR))
                    COMPARE.push(EXPR.pop());
//                if(currentIR.second.equals(IRProduceListener.COMPARE))
//                    COMPARE.setValue(COMPARE);
                if(currentIR.second.equals("true"))
                    COMPARE.push(new Value(Value.BOOL, true));
                if(currentIR.second.equals("false"))
                    COMPARE.push(new Value(Value.BOOL, false));
                break;
            case IRProduceListener.EXPR:
                if(currentIR.second.equals(IRProduceListener.VARIABLE)){
                    EXPR.push(symlist.get(VARIABLE.pop().intValue()).value);
                } else if(currentIR.second.equals(IRProduceListener.EXPR)){
                    EXPR.push(EXPR.pop());
                } else if(isInteger(currentIR.second)){
                    EXPR.push(new Value(Value.INT,Integer.parseInt(currentIR.second)));
                } else {
                    EXPR.push(new Value(Value.DOUBLE,Double.parseDouble(currentIR.second)));
                }
                break;
            default:
                int address = isValExist(currentIR.dest);
                symlist.get(address).value.setValue(EXPR.pop());
        }

    }

    public void ASSIGN (){
        //(ASSIGN, EXPR, null, VARIABLE)
        symlist.get(VARIABLE.pop()).value.setValue(EXPR.pop());
    }

    public void JMP(){
        //(JMP, COMPARE, BACKWARD/FORWARD, Line)
        //(JMP, null, null, Line)
        if(currentIR.second!=null){
            if(COMPARE.pop().getValue()!=0)
                return;
        }
        currentLine = Integer.parseInt(currentIR.dest)-1;
        return;
    }

    public void RELATION (){
        //(RELATION, Expr, COMPARE, COMPARE)
        double b = EXPR.pop().getValue();
        double a = EXPR.pop().getValue();
        boolean flag = false;
        switch (currentIR.first) {
            case BIGGER_THAN:
                if(a>b) flag=true;
                break;
            case LESS_THAN:
                if(a<b) flag=true;
                break;
            case NO_LESS:
                if(a>=b) flag=true;
                break;
            case NO_MORE:
                if(a<=b) flag=true;
                break;
            case EQ:
                if(a==b) flag=true;
                break;
            case UNEQ:
                if(a!=b) flag=true;
                break;
        }
        COMPARE.push(new Value(Value.BOOL,flag));
    }

    public void LOG_NOT(){
        if(COMPARE.peek().mBool==true) {
            COMPARE.peek().mBool= false;
        } else if (COMPARE.pop().mBool==false){
            COMPARE.peek().mBool = true;
        }
    }

    public void Minus(){
        //(Minus, EXPR,null, EXPR)
        if(EXPR.peek().mType == Value.INT){
            EXPR.peek().mInt = -EXPR.peek().mInt;
        }
        if(EXPR.peek().mType == Value.DOUBLE) {
            EXPR.peek().mDouble = -EXPR.peek().mDouble;
        }
    }

    public void READ(){
        //(READ, STDIN, null, VARIABLE)
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine();
        scanner.close();
        switch (symlist.get(VARIABLE.peek()).value.mType) {
            case Value.BOOL:
                symlist.get(VARIABLE.pop()).value.setValue(new Value(Value.BOOL,Boolean.valueOf(s)));
                break;
            case Value.INT:
                symlist.get(VARIABLE.pop()).value.setValue(new Value(Value.INT,Integer.valueOf(s)));
                break;
            case Value.DOUBLE:
                symlist.get(VARIABLE.pop()).value.setValue(new Value(Value.DOUBLE,Double.valueOf(s)));
                break;
        }
    }

    public void WRITE(){
        //(WRITE, EXPR, null, STDOUT)
        if(currentIR.dest.equals("STDOUT"))
            switch (EXPR.peek().mType) {
                case Value.BOOL:
                    System.out.print(EXPR.pop().mBool+"\n");
                    break;
                case Value.INT:
                    System.out.print(EXPR.pop().mInt+"\n");
                    break;
                case Value.DOUBLE:
                    System.out.print(EXPR.pop().mDouble+"\n");
                    break;
            }
        }

    public void DECL () throws Exception {
        //(DECL,TYPE,null/EXPR, ID)
//        int exists = isValExist(currentIR.dest);
//        if(exists!=-1) {
//            if (symlist.get(exists).level < level) {
//                newsym.next = symlist.get(exists);
//                symlist.push(newsym);
//            } else {
//                throw new InterruptedException("Error: illegal variable declaration, declare the same variable in the same scope twice!");
//            }
//        }
        switch (TYPE) {
            case Value.INT:
                symlist.push(new Symbol(currentIR.dest, Value.INT, level));
                break;
            case Value.DOUBLE:
                symlist.push(new Symbol(currentIR.dest, Value.DOUBLE, level));
                break;
            case Value.BOOL:
                symlist.push(new Symbol(currentIR.dest, Value.BOOL, level));
                break;
        }
        if (currentIR.third != null) {
            int a = Integer.parseInt(currentIR.third);
            for (int i =1;i<a;i++){
                switch (TYPE) {
                    case Value.INT:
                        symlist.push(new Symbol(currentIR.dest, Value.INT, level));
                        break;
                    case Value.DOUBLE:
                        symlist.push(new Symbol(currentIR.dest, Value.DOUBLE, level));
                        break;
                    case Value.BOOL:
                        symlist.push(new Symbol(currentIR.dest, Value.BOOL, level));
                        break;
                }
            }
        }
    }

    public void PLUSMINUS() throws Exception{
        Value b = EXPR.pop();
        Value a = EXPR.pop();
        if(a.mType==Value.INT&&b.mType==Value.INT) {
            switch (currentIR.first) {
                case PLUS:
                    EXPR.push(new Value(Value.INT,new Double(a.getValue()+b.getValue()).intValue()));
                    break;
                case MINUS:
                    EXPR.push(new Value(Value.INT,new Double(a.getValue()-b.getValue()).intValue()));;
                    break;
            }
        } else {
            switch (currentIR.first) {
                case PLUS:
                    EXPR.push(new Value(Value.DOUBLE,a.getValue()+b.getValue()));
                    break;
                case MINUS:
                    EXPR.push(new Value(Value.DOUBLE,a.getValue()-b.getValue()));
                    break;
            }
        }
    }

    public void MULDIV() throws Exception{
        Value b = EXPR.pop();
        Value a = EXPR.pop();

        if(a.mType==Value.INT&&b.mType==Value.INT) {
            switch (currentIR.first) {
                case MUL:
                    EXPR.push(new Value(Value.INT,new Double(a.getValue()*b.getValue()).intValue()));
                    break;
                case DIV:
                    if(b.getValue()==0){
                        throw new InterruptedException("0 can not be divided!");
                    }
                    EXPR.push(new Value(Value.INT,new Double(a.getValue()/b.getValue()).intValue()));
                    break;
                case MOD:
                    if(a.getValue()==0){
                        throw new InterruptedException("0 can not be divided!");
                    }
                    EXPR.push(new Value(Value.INT,new Double(a.getValue()%b.getValue()).intValue()));
                    break;
            }
        } else {
            switch (currentIR.first) {
                case MUL:
                    EXPR.push(new Value(Value.DOUBLE,a.getValue()*b.getValue()));
                    break;
                case DIV:
                    EXPR.push(new Value(Value.DOUBLE,a.getValue()/b.getValue()));;
                    if(b.getValue()==0){
                        throw new InterruptedException("0 can not be divided!");
                    }
                    break;
                case MOD:
                    throw new InterruptedException("Double can not do mod operation!");
            }
        }
    }

    public static boolean isInteger(String str) {
        Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$");
        return pattern.matcher(str).matches();
    }

    public static int isValExist(String name){
        ListIterator<Symbol> it = symlist.listIterator();
        while (it.hasNext()){
            it.next();
        }
        while (it.hasPrevious()){
            Symbol exist = it.previous();
            if(exist.name.equals(name))
                return it.previousIndex()+1;
        }
        return -1;
    }
//    public void OUT(){
//        ListIterator<Symbol> it = symlist.listIterator();
//        while (it.hasNext()){
//            it.next();
//        }
//        while (it.hasPrevious()){
//            Symbol exist = it.previous();
//            int index;
//            if(exist.level==level){
//                index = it.previousIndex()+1;
//                symlist.remove(index);
//            }
//
//        }
//        level--;
//    }

//    public static Value getValue(String name,ArrayList<Symbol> symlist) throws InterruptedException{
//        if(isInteger(name)){
//            return Integer.parseInt(name);
//        }
//        else{
//            if(isValExist(name,symlist)!=null){
//                return isValExist(name,symlist).value.mInt;
//            }
//            else{
//                throw new InterruptedException("Undeclared variable is used");
//            }
//
//        }
//
//    }
}
